"""
小红书客服咨询系统主应用模块

该模块实现了完整的客服咨询系统，包括：
1. FastAPI应用初始化与配置
2. 数据库连接管理
3. CORS跨域配置
4. WebSocket连接管理（客服、用户、聊天室）
5. HTTP接口实现
6. 定时任务调度
"""

import uvicorn
from fastapi import FastAPI, Request, HTTPException
import time
import json

from starlette.middleware.cors import CORSMiddleware
from tortoise.contrib.fastapi import register_tortoise
from config.config import TORTOISE_ORM
from fastapi import WebSocket, WebSocketDisconnect
from typing import List, Dict, Optional
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime

from view.users import users_router

# ==================== 应用初始化 ====================
"""创建FastAPI应用实例"""
app = FastAPI()

"""初始化APScheduler调度器（使用后台线程执行任务）"""
scheduler = BackgroundScheduler()

# ==================== 数据库配置 ====================
"""
注册Tortoise ORM数据库连接
该方法会在fastapi启动时触发，内部通过传递进去的app对象，监听服务启动和终止事件
当检测到启动事件时，会初始化Tortoise对象，如果generate_schemas为True则还会进行数据库迁移
当检测到终止事件时，会关闭连接
"""
register_tortoise(
    app,
    config=TORTOISE_ORM,
    # generate_schemas=True,  # 如果数据库为空，则自动生成对应表单，生产环境不要开
    # add_exception_handlers=True,  # 生产环境不要开，会泄露调试信息
)

# ==================== 路由注册 ====================
"""将用户相关路由包含到主应用"""
app.include_router(users_router)

# ==================== CORS跨域配置 ====================
"""
定义允许跨域请求的来源列表
CORS（Cross-Origin Resource Sharing）是一种安全机制，
用于限制一个源（域）的文档或脚本如何与另一个源的资源进行交互
"""
origins = [
    "http://localhost",
    "http://localhost:5173",
    "http://localhost:3000",
    # 可以添加更多允许的域名，如生产环境域名
    "https://yourproductiondomain.com"
]

"""
添加 CORS 中间件
allow_origins: 允许的来源列表
allow_credentials: 允许携带凭证（如 cookies）
allow_methods: 允许的 HTTP 方法，* 表示允许所有方法
allow_headers: 允许的 HTTP 请求头，* 表示允许所有头
"""
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # 允许的来源列表
    allow_credentials=True,  # 允许携带凭证（如 cookies）
    allow_methods=["*"],  # 允许的 HTTP 方法，* 表示允许所有方法
    allow_headers=["*"],  # 允许的 HTTP 请求头，* 表示允许所有头
)

# ==================== 基础路由 ====================
@app.get("/")
def read_root():
    """
    根路径访问接口
    返回简单的欢迎信息
    
    Returns:
        dict: 包含欢迎信息的字典
    """
    return {"Hello": "World"}

# ==================== WebSocket连接管理 ====================
"""
导入异步IO模块，用于处理并发连接
创建全局事件循环对象
"""
import asyncio

loop = asyncio.get_event_loop()

# ============ 连接管理 ============
"""
客服连接管理字典：存储客服WebSocket连接
键为客服ID，值为对应的WebSocket连接对象
"""
service_connections: Dict[str, WebSocket] = {}

"""
用户连接管理字典：存储用户WebSocket连接
键为用户ID，值为对应的WebSocket连接对象
"""
user_connections: Dict[str, WebSocket] = {}

"""
聊天室管理字典：存储聊天室连接信息
键为房间ID（格式：f"{user_id}_{service_id}"），值为包含用户和服务连接的字典
"""
chat_rooms: Dict[str, Dict[str, WebSocket]] = {}

"""
咨询请求列表：存储客服的咨询请求队列（前端维护，后端推送通知）
键为客服ID，值为该客服的所有咨询请求列表
每个请求包含：用户ID、客服ID、创建时间、房间ID等信息
"""
consultation_requests: Dict[str, List[Dict]] = {}

# ============ 工具函数 ============
async def send_json(websocket: WebSocket, data: dict):
    """
    发送JSON消息到指定WebSocket连接
    
    Args:
        websocket (WebSocket): 目标WebSocket连接对象
        data (dict): 要发送的数据字典
    
    Raises:
        WebSocketDisconnect: 当WebSocket连接断开时抛出异常
    """
    await websocket.send_text(json.dumps(data, ensure_ascii=False))





def get_room_id(user_id: str, service_id: str) -> str:
    """
    生成聊天室ID
    
    Args:
        user_id (str): 用户ID
        service_id (str): 客服ID
    
    Returns:
        str: 生成的房间ID，格式为"{user_id}_{service_id}"
    """
    return f"{user_id}_{service_id}"


def match_service(user_id: str) -> str:
    """
    匹配可用客服（暂时写死）
    实际应该根据客服在线状态、负载等匹配
    
    Args:
        user_id (str): 用户ID
    
    Returns:
        str: 匹配到的客服ID
    
    Note:
        当前实现是临时方案，实际应根据客服在线状态、工作负载等因素智能分配
    """
    # 写死返回第一个在线的客服，如果没有则返回默认客服ID
    if service_connections:
        return list(service_connections.keys())[0]
    return "service_1001"  # 默认客服ID

# ============ HTTP接口：开始咨询 ============
@app.post("/api/customer-service/start-consult")
async def start_consultation(request: Request):
    """
    用户点击"开始咨询"按钮的HTTP接口
    主要功能：
    1. 根据用户ID匹配客服（写死）
    2. 创建聊天室
    3. 通过WebSocket通知客服有新咨询请求
    
    Args:
        request (Request): HTTP请求对象，包含用户ID等信息
    
    Returns:
        dict: 包含咨询请求信息的响应字典
    
    Raises:
        HTTPException: 当用户ID为空或服务器内部错误时抛出相应异常
    
    Example:
        POST /api/customer-service/start-consult
        Request Body: {"user_id": "user_001"}
        Response: {
            "code": 200,
            "message": "咨询请求已创建",
            "data": {
                "user_id": "user_001",
                "service_id": "service_1001",
                "room_id": "user_001_service_1001",
                "created_at": "2025-11-04T10:00:00",
                "status": "pending"
            }
        }
    """
    try:
        data = await request.json()
        user_id = str(data.get("user_id"))
        
        if not user_id:
            raise HTTPException(status_code=400, detail="用户ID不能为空")
        
        # 匹配客服（暂时写死）
        service_id = match_service(user_id)
        room_id = get_room_id(user_id, service_id)
        
        # 创建咨询请求信息
        request_info = {
            "user_id": user_id,
            "service_id": service_id,
            "room_id": room_id,
            "created_at": datetime.now().isoformat(),
            "status": "pending"  # pending, accepted, rejected
        }
        
        # 添加到咨询请求列表
        if service_id not in consultation_requests:
            consultation_requests[service_id] = []
        
        # 检查是否已存在相同的请求
        existing = [r for r in consultation_requests[service_id] if r["user_id"] == user_id and r["status"] == "pending"]
        if existing:
            # 如果已有待处理的请求，返回已有信息
            return {
                "code": 200,
                "message": "咨询请求已存在",
                "data": existing[0]
            }
        
        consultation_requests[service_id].append(request_info)
        
        # 通过WebSocket通知客服
        if service_id in service_connections:
            await send_json(service_connections[service_id], {
                "type": "new_consultation",
                "data": request_info
            })
            print(f"[通知] 已向客服 {service_id} 推送新咨询请求：用户 {user_id}")
        else:
            print(f"[警告] 客服 {service_id} 未连接，无法推送咨询请求")
        
        # 通知用户（如果用户已连接）
        if user_id in user_connections:
            await send_json(user_connections[user_id], {
                "type": "consultation_created",
                "data": request_info
            })
        
        return {
            "code": 200,
            "message": "咨询请求已创建",
            "data": request_info
        }
        
    except Exception as e:
        print(f"[错误] 创建咨询请求失败: {e}")
        raise HTTPException(status_code=500, detail=str(e))

# ============ WebSocket：客服连接 ============
@app.websocket("/ws/service/{service_id}")
async def service_websocket(websocket: WebSocket, service_id: str):
    """
    客服WebSocket连接端点
    用于客服接收新咨询请求通知及与用户通信
    
    Args:
        websocket (WebSocket): 客服的WebSocket连接对象
        service_id (str): 客服ID，从URL路径参数中获取
    
    功能：
    1. 接受客服WebSocket连接
    2. 存储客服连接信息
    3. 发送待处理的咨询请求列表
    4. 处理客服接受咨询请求的消息
    5. 处理客服断开连接的情况
    
    WebSocket消息格式：
    - 接收消息格式: {"type": "accept_consultation", "room_id": "xxx"}
    - 发送消息格式: 
      {"type": "new_consultation", "data": {...}} - 新咨询请求通知
      {"type": "consultation_list", "data": [...]} - 待处理请求列表
    """
    await websocket.accept()
    
    # 存储客服连接
    service_connections[service_id] = websocket
    consultation_requests[service_id] = consultation_requests.get(service_id, [])
    
    print(f"[客服连接] {service_id} 已连接，当前在线客服数：{len(service_connections)}")
    
    # 发送已有的待处理请求列表
    pending_requests = [r for r in consultation_requests.get(service_id, []) if r["status"] == "pending"]
    if pending_requests:
        await send_json(websocket, {
            "type": "consultation_list",
            "data": pending_requests
        })
    
    try:
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            message_type = message.get("type")
            
            if message_type == "accept_consultation":
                # 客服接受咨询请求
                room_id = message.get("room_id")
                # 更新请求状态
                for req in consultation_requests.get(service_id, []):
                    if req["room_id"] == room_id:
                        req["status"] = "accepted"
                        # 通知用户
                        user_id = req["user_id"]
                        if user_id in user_connections:
                            await send_json(user_connections[user_id], {
                                "type": "consultation_accepted",
                                "data": {"room_id": room_id, "service_id": service_id}
                            })
                        break
            
            print(f"[客服消息] {service_id}: {message}")
            
    except WebSocketDisconnect:
        service_connections.pop(service_id, None)
        print(f"[客服断开] {service_id} 已断开，当前在线客服数：{len(service_connections)}")

# ============ WebSocket：用户连接 ============
@app.websocket("/ws/user/{user_id}")
async def user_websocket(websocket: WebSocket, user_id: str):
    """
    用户WebSocket连接端点
    用于用户发起咨询请求及与客服通信
    
    Args:
        websocket (WebSocket): 用户的WebSocket连接对象
        user_id (str): 用户ID，从URL路径参数中获取
    
    功能：
    1. 接受用户WebSocket连接
    2. 存储用户连接信息
    3. 处理用户发起咨询请求的消息
    4. 处理用户断开连接的情况
    
    WebSocket消息格式：
    - 接收消息格式: {"type": "start_consult"}
    - 发送消息格式:
      {"type": "consultation_created", "code": 200, "message": "...", "data": {...}}
      {"type": "consultation_accepted", "data": {...}}
      {"type": "error", "code": 500, "message": "..."}
    """
    await websocket.accept()
    
    # 存储用户连接
    user_connections[user_id] = websocket
    
    print(f"[用户连接] {user_id} 已连接，当前在线用户数：{len(user_connections)}")
    
    try:
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            message_type = message.get("type")
            
            # 处理创建咨询请求
            if message_type == "start_consult":
                try:
                    # 匹配客服（暂时写死）
                    service_id = match_service(user_id)
                    room_id = get_room_id(user_id, service_id)
                    
                    # 创建咨询请求信息
                    request_info = {
                        "user_id": user_id,
                        "service_id": service_id,
                        "room_id": room_id,
                        "created_at": datetime.now().isoformat(),
                        "status": "pending"  # pending, accepted, rejected
                    }
                    
                    # 添加到咨询请求列表
                    if service_id not in consultation_requests:
                        consultation_requests[service_id] = []
                    
                    # 检查是否已存在相同的请求
                    existing = [r for r in consultation_requests[service_id] if r["user_id"] == user_id and r["status"] == "pending"]
                    if existing:
                        # 如果已有待处理的请求，返回已有信息
                        await send_json(websocket, {
                            "type": "consultation_created",
                            "code": 200,
                            "message": "咨询请求已存在",
                            "data": existing[0]
                        })
                    else:
                        consultation_requests[service_id].append(request_info)
                        
                        # 通过WebSocket通知客服
                        if service_id in service_connections:
                            await send_json(service_connections[service_id], {
                                "type": "new_consultation",
                                "data": request_info
                            })
                            print(f"[通知] 已向客服 {service_id} 推送新咨询请求：用户 {user_id}")
                        else:
                            print(f"[警告] 客服 {service_id} 未连接，无法推送咨询请求")
                        
                        # 返回创建成功的消息
                        await send_json(websocket, {
                            "type": "consultation_created",
                            "code": 200,
                            "message": "咨询请求已创建",
                            "data": request_info
                        })
                        
                except Exception as e:
                    print(f"[错误] 创建咨询请求失败: {e}")
                    await send_json(websocket, {
                        "type": "error",
                        "code": 500,
                        "message": f"创建咨询请求失败: {str(e)}"
                    })
            
            # 其他消息类型可以在这里扩展
            else:
                print(f"[用户消息] {user_id}: {message}")
            
    except WebSocketDisconnect:
        user_connections.pop(user_id, None)
        print(f"[用户断开] {user_id} 已断开，当前在线用户数：{len(user_connections)}")

# ============ WebSocket：聊天室 ============
@app.websocket("/ws/chat/{room_id}")
async def chat_room_websocket(websocket: WebSocket, room_id: str):
    """
    聊天室WebSocket连接端点
    用于用户和客服之间的实时双向聊天
    
    Args:
        websocket (WebSocket): 聊天室连接对象
        room_id (str): 房间ID，格式为"{user_id}_{service_id}"，从URL路径参数中获取
    
    功能：
    1. 接受聊天室WebSocket连接
    2. 解析房间ID获取用户ID和客服ID
    3. 存储聊天室连接信息
    4. 通知双方成员加入
    5. 转发聊天消息
    6. 处理连接断开情况
    
    WebSocket消息格式：
    - 连接初始化消息: {"identity": "user|service", "client_id": "xxx"}
    - 聊天消息格式: {"type": "chat", "content": "消息内容", "sender": "发送者ID", "fromHumanService": true/false}
    - 系统消息格式: {"type": "user_joined|service_joined", "data": {...}}
    """
    await websocket.accept()
    
    # 解析room_id获取user_id和service_id
    parts = room_id.split("_")
    if len(parts) < 2:
        await websocket.close(code=1008, reason="Invalid room_id format")
        return
    
    identity = None
    client_id = None
    
    try:
        # 接收身份标识（前端需要在连接时发送）
        init_message = await websocket.receive_text()
        init_data = json.loads(init_message)
        identity = init_data.get("identity")  # "user" or "service"
        client_id = init_data.get("client_id")
        
        if not identity or not client_id:
            await websocket.close(code=1008, reason="Missing identity or client_id")
            return
        
        # 存储连接
        if identity == "user":
            chat_rooms[room_id] = chat_rooms.get(room_id, {})
            chat_rooms[room_id]["user"] = websocket
            print(f"[聊天室] 用户 {client_id} 进入房间 {room_id}")
        elif identity == "service":
            chat_rooms[room_id] = chat_rooms.get(room_id, {})
            chat_rooms[room_id]["service"] = websocket
            print(f"[聊天室] 客服 {client_id} 进入房间 {room_id}")
        else:
            await websocket.close(code=1008, reason="Invalid identity")
            return
        
        # 通知对方已进入
        if identity == "user" and "service" in chat_rooms.get(room_id, {}):
            await send_json(chat_rooms[room_id]["service"], {
                "type": "user_joined",
                "data": {"user_id": client_id}
            })
        elif identity == "service" and "user" in chat_rooms.get(room_id, {}):
            await send_json(chat_rooms[room_id]["user"], {
                "type": "service_joined",
                "data": {"service_id": client_id}
            })
        
        # 消息循环
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            message_type = message.get("type", "chat")
            
            if message_type == "chat":
                # 聊天消息：转发给对方
                content = message.get("content", "")
                sender = message.get("sender", client_id)
                timestamp = datetime.now().isoformat()
                from_human_service = message.get("fromHumanService", False)  # 保留消息来源标识
                
                chat_message = {
                    "type": "chat",
                    "content": content,
                    "sender": sender,
                    "timestamp": timestamp,
                    "room_id": room_id,
                    "fromHumanService": from_human_service  # 转发消息来源标识
                }
                
                # 转发给对方
                if identity == "user" and "service" in chat_rooms.get(room_id, {}):
                    await send_json(chat_rooms[room_id]["service"], chat_message)
                elif identity == "service" and "user" in chat_rooms.get(room_id, {}):
                    await send_json(chat_rooms[room_id]["user"], chat_message)
                
                print(f"[聊天消息] 房间 {room_id} - {sender}: {content}")
            
    except WebSocketDisconnect:
        # 清理连接
        if room_id in chat_rooms and identity and client_id:
            if identity == "user":
                chat_rooms[room_id].pop("user", None)
            elif identity == "service":
                chat_rooms[room_id].pop("service", None)
            
            # 如果房间为空，删除房间
            if not chat_rooms[room_id]:
                chat_rooms.pop(room_id, None)
        
        print(f"[聊天室断开] {client_id or 'Unknown'} 离开房间 {room_id}")
    except Exception as e:
        print(f"[聊天室错误] 房间 {room_id}: {e}")
        if room_id in chat_rooms and identity:
            if identity == "user":
                chat_rooms[room_id].pop("user", None)
            elif identity == "service":
                chat_rooms[room_id].pop("service", None)

# ============ 定时任务 ============
def print_current_time():
    """
    定时任务函数（示例）
    打印当前时间和系统连接状态信息
    
    功能：
    1. 输出当前系统时间
    2. 统计并输出在线客服数、在线用户数、聊天室数
    """
    global service_connections, loop
    
    print(f"[定时任务] 当前时间: {datetime.now()}")
    print(f"[状态] 在线客服数: {len(service_connections)}, 在线用户数: {len(user_connections)}, 聊天室数: {len(chat_rooms)}")


def heavy_task():
    """
    示例：耗时任务（演示调度器的异步执行）
    模拟执行耗时操作的任务
    
    功能：
    1. 模拟耗时操作（sleep 3秒）
    2. 输出任务开始和结束信息
    """
    print("开始执行耗时任务...")
    time.sleep(3)  # 模拟耗时操作
    print("耗时任务执行完成")


# 4. 向调度器添加任务（配置触发规则）
def add_jobs():
    """
    向调度器添加定时任务
    
    包含三种类型的定时任务示例：
    1. interval类型：按固定时间间隔执行
    2. cron类型：按CRON表达式执行（类似Linux的crontab）
    3. date类型：在特定时间点执行一次
    """
    # 任务1：每隔5秒执行一次（interval触发）
    scheduler.add_job(
        func=print_current_time,
        trigger="interval",  # 间隔触发
        seconds=1,  # 间隔5秒
        id="time_printer"  # 任务唯一ID（用于后续操作，如删除）
    )

    # 任务2：每天12:30执行（cron触发，类似Linux的crontab）
    # scheduler.add_job(
    #     func=heavy_task,
    #     trigger="cron",  # CRON表达式触发
    #     hour=12,
    #     minute=30,
    #     id="daily_task"
    # )

    # 任务3：在特定时间执行一次（date触发）
    # scheduler.add_job(
    #     func=print_current_time,
    #     trigger="date",  # 特定时间触发
    #     run_date=datetime(2025, 12, 31, 23, 59, 59),  # 2025年12月31日23:59:59
    #     id="specific_time_task"
    # )


# 5. 利用FastAPI生命周期事件管理调度器
@app.on_event("startup")
def startup_event():
    """
    应用启动事件处理函数
    在FastAPI应用启动时执行，用于初始化调度器
    
    功能：
    1. 获取事件循环对象
    2. 添加定时任务
    3. 启动调度器
    """
    global loop
    # loop = asyncio.get_event_loop()

    add_jobs()
    scheduler.start()
    print("调度器已启动")


@app.on_event("shutdown")
def shutdown_event():
    """
    应用关闭事件处理函数
    在FastAPI应用关闭时执行，用于清理资源
    
    功能：
    1. 关闭调度器
    2. 输出关闭信息
    """
    scheduler.shutdown()
    print("调度器已关闭")


# ==================== 应用入口 ====================
if __name__ == '__main__':
    """
    程序入口点
    使用uvicorn启动FastAPI应用
    
    参数说明：
    - "index:app": 指定要运行的应用模块和实例
    - host="127.0.0.1": 监听地址
    - port=8000: 监听端口
    - reload=True: 开启热重载（开发模式下方便调试）
    """
    uvicorn.run("index:app", host="127.0.0.1", port=8000, reload=True)